"""
This module contains classes that allow the user to view and edit
Landis-II input files.

    - EditView: Default implementation that allows the user to open
                multiple files to view and edit
    - Page: Interface for views that are used as pages in the EditView
            notebook
    - BasicEditor: Used as pages for the EditView
"""
"""
   Copyright 2011 Gabriel Elkind and Brian Gapinski

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
"""
import logging

import wx
import wx.lib.agw.flatnotebook as fnb

logger = logging.getLogger('editor')

class EditView(fnb.FlatNotebook):
    """
    This is a multitabbed editor made specifically for the 
    Landis-II-LIME project. It allows a user to edit multiple
    files. This class uses the wx.lib.agw.FlatNotebook class.

    @type _lime: Lime
    @ivar _lime: GUI container

    @type _model: Model
    @ivar _model: The model representing the files of Landis-II

    @type _default_font: wx.TextAttr
    @ivar _default_font: The default font for each text window
    """
    def __init__(self, lime, *args, **kwargs):
        """
        This class is designed to be a multi-tabbed editor
        for the LIME project by extending the wx.Notebook class.

        @param lime: The parent of the Editor module
        @type lime: Lime class
        """
        logger.debug('editor created')
        fnb.FlatNotebook.__init__(self, parent=lime, \
            size=(600,400), *args, **kwargs)

        style = fnb.FNB_FF2|fnb.FNB_X_ON_TAB|fnb.FNB_NO_X_BUTTON

        self.SetWindowStyleFlag(style)

        self._model = None

        # Sets the default font for each editor
        self._default_font = wx.TextAttr()
        font = wx.Font(10, wx.TELETYPE, wx.NORMAL, wx.NORMAL, False)
        self._default_font.SetFont(font)

        # Creates a temporary display until a project is opened
        temp = BasicEditor(self, 'New Project')
        self.AddPage(temp, 'New Project')

    def Notify(self, filename=''):
        """
        Informs this object that a file has changed
        within the central model and makes relevant
        changes

        @param filename: The file which has changed. The model
                         has been changed if '' is passed.
        @type filename:  string
        """
        logger.debug('Notify request received for file: %s', filename)
        
        # Changes the model
        if filename == '':
            self.AddFile(self._model.GetTree().GetName())
        
        # Searches for the page with filename
        else:
            for i in range(self.GetPageCount()):
                if self.GetPage(i).GetName() == filename:
                    
                    # Checks the sync state of the file
                    if self._model.GetSync(filename):
                        self.SetPageText(i, self.GetPage(i).GetName())
                        self.Bind(wx.EVT_TEXT, self._OnUpdate, self.GetPage(i))
                    else:
                        self.SetPageText(i, self.GetPage(i).GetName()+'*')
                    break

    def SetModel(self, model):
        """
        Sets the model for the EditView

        @type model: Model
        @param model: The new model to be set        
        """
        self._model = model

    def AddFile(self, newfile):
        """
        This adds a new tab in the editor to edit a file.

        @param newfile: The file to be edited
        @type newfile: string
        """
        logger.debug('Adding file to editor')
        
        # Used to have a default page. Deletes when project open
        if self.GetPageText(self.GetSelection()) == 'New Project':
            logger.debug('Default page deleted')
            self.DeletePage(0)
        
        # Switches to the tab with the file
        for i in range(self.GetPageCount()):
            if self.GetPage(i).GetName() == newfile:
                self.SetSelection(i)
                break

        # Creates a new tab if the file is not already open
        else:
            logger.debug('File not found. Adding new tab')
            editor = BasicEditor(self, newfile)
            editor.SetDefaultStyle(self._default_font)
            content = open(newfile, 'r+').read()
            editor.SetContent(content)
            self.AddPage(editor, newfile)
            self.SetSelection(int(self.GetPageCount())-1)
            self.Bind(wx.EVT_TEXT, self._OnUpdate, editor)

    def Copy(self):
        """
        Copies the highlighted text to the clipboard
        """
        self.GetCurrentPage().Copy()

    def Cut(self):
        """
        Cuts the highlighted text
        """
        self.GetCurrentPage().Cut()

    def Paste(self):
        """
        Copies the text from the clipboard to the open buffer
        """
        self.GetCurrentPage().Paste()

    def GetCurrText(self):
        """
        Returns the text of the currently viewed tab

        @return: string
        """
        return self.GetCurrentPage().GetContent()

    
    def GetCurrFile(self):
        """
        Returns the name of the file currently being edited

        @return: The name of the file being edited
        @rtype: string
        """
        return self.GetCurrentPage().GetName()
    
    def GetText(self, filename):
        """
        Returns the text at given index i.

        @param filename: The name of the file to find
        @type filename: string

        @return: The text contained in filename
        @rtype: string
        """
        for i in range(self.GetPageCount()):
            if self.GetPage(i).GetName() == filename:
                return self.GetPage(i).GetContent()

    def GetFile(self, i):
        """
        Returns the text at given index i.

        @param i: The index of the tab
        @type i: int

        @return: int
        """
        return self.GetPage(i).GetName()

    def GetTabCount(self):
        """
        This method returns the number of files
        that are currently being edited.
        
        @return: The number of open tabs
        @rtype: int
        """
        return self.GetPageCount()

    def GetAll(self):
        """
        Retuns a dictionary of all currently open files that have their
        buffers as value.

        @return: A dictionary of filenames to buffers
        @rtype: dictionary: str->str
        """
        files = {}
        for i in range(self.GetPageCount()):
            filename = self.GetPage(i).GetName()
            files[filename] = self.GetPage(i).GetContent()
        return files

    def OpenError(self, filename, line, error):
        """
        Brings up the file that contains the error and highlights the text.

        @param filename: The name of the file with the error
        @type filename: string

        @param line: The line that contains the error
        @type line: int

        @param error: The error
        @type error: string
        """
        logger.debug('Opening file with error')
        self.AddFile(filename)

        # Gets the page with the file
        page = self.GetCurrentPage()

        # Gets the start of the error
        start = page.GetValue().find(error)
        logger.debug('Error starts at: %i', start)

        # Gets the end location of the error
        end = start + len(error)
        logger.debug('Error ends at: %i', end)

        # Creates the font to style the error
        style = wx.TextAttr()
        font = wx.Font(10, wx.TELETYPE, wx.NORMAL, wx.NORMAL, True)
        style.SetFont(font)
        style.SetTextColour(wx.RED)

        # Moves the text cursor to the error
        page.SetInsertionPoint(end)

        page.SetStyle(start, end, style)
        logger.debug('Font changed')

    def _OnUpdate(self, event):
        """
        This method creates an event when the text has been modified
        in a window and tells lime.
        """
        logger.debug('Update notification received')
        self._model.SetSync(self.GetPage(self.GetSelection()).GetName(), False)
        self.Unbind(wx.EVT_TEXT, self.GetCurrentPage())

class Page(object):
    """
    This is an abstract class representing objects that can 
    be used inside of a notebook object.
    """
    def GetName(self):
        """
        Returns the name of the file associated to this editor
        @return: The name of the file associated to this editor
        @rtype: string
        """
        raise NotImplemented

    def GetContent(self):
        """
        Returns the data inside this page

        @returns: The data
        @rtype:   some sort of raw data
        """
        raise NotImplemented

    def SetContent(self,content):
        """
        Sets the content of this buffer

        @param content: The data to be contained by this page
        @type content: some kind of file data
        """
        raise NotImplemented


class BasicEditor(wx.TextCtrl,Page):
    """
    This class serves as a basic editor for the EditView class.
    This extends a TextCtrl class and allows the user to edit
    plain text files.

    @type _name: string
    @ivar _name: The name of the file associated to this editor
    """
    def __init__(self, editview, name):
        """
        Creates a basic editor that extends a wx.TextCtrl

        @type editview: wx.Window
        @oaram editview: The parent of this editor

        @type name: string
        @param name: The name of the file associated to this editor
        """
        wx.TextCtrl.__init__(self, editview, wx.ID_ANY, style=wx.TE_MULTILINE)
        self._name = name

    def GetName(self):
        """
        Returns the name of the file associated to this editor

        @return: The name of the file associated to this editor
        @rtype: string
        """
        return self._name

    def GetContent(self):
        """
        Returns whatever kind of data is contained in this page

        @returns: The content
        @rtype: some kind of raw data
        """
        return self.GetValue()

    def SetContent(self,content):
        """
        Sets the content of this buffer
    
        @param content: The data to be contained by this page
        @type content: some kind of file data
        """
        self.Clear()
        self.AppendText(content)
         
